import * as fs from 'fs';
import * as path from 'path';
import * as assert from 'assert';
import { loadConfig, svelte } from '../helpers';
// keep source-map at version 0.7.x
// https://github.com/mozilla/source-map/issues/400
import { getLocator } from 'locate-character';
import { SourceMapConsumer } from 'source-map';


describe('sourcemaps', () => {
	fs.readdirSync(`${__dirname}/samples`).forEach(dir => {
		if (dir[0] === '.') return;

		const config = loadConfig(`${__dirname}/samples/${dir}/_config.js`);

		// add .solo to a sample directory name to only run that test
		const solo = config.solo || /\.solo/.test(dir);
		const skip = config.skip || /\.skip/.test(dir);

		if (solo && process.env.CI) {
			throw new Error('Forgot to remove `solo: true` from test');
		}

		(solo ? it.only : skip ? it.skip : it)(dir, async () => {
			const { test } = require(`./samples/${dir}/test.js`);
			const inputFile = path.resolve(`${__dirname}/samples/${dir}/input.svelte`);
			const outputName = '_actual';
			const outputBase = path.resolve(`${__dirname}/samples/${dir}/${outputName}`);

			const inputCode = fs.readFileSync(inputFile, 'utf-8');
			const input = {
				code: inputCode,
				locate: getLocator(inputCode),
				locate_1: getLocator(inputCode, { offsetLine: 1 })
			};

			const preprocessed = await svelte.preprocess(
				input.code,
				config.preprocess || {},
				config.options || {
					filename: 'input.svelte'
				}
			);

			const { js, css } = svelte.compile(
				preprocessed.code, {
				filename: 'input.svelte',
				// filenames for sourcemaps
				sourcemap: preprocessed.map,
				outputFilename: `${outputName}.js`,
				cssOutputFilename: `${outputName}.css`,
				...(config.compile_options || {})
			});

			js.code = js.code.replace(
				/generated by Svelte v\d+\.\d+\.\d+/,
				match => match.replace(/\d/g, 'x')
			);

			fs.writeFileSync(`${outputBase}.svelte`, preprocessed.code);
			if (preprocessed.map) {
				fs.writeFileSync(
					`${outputBase}.svelte.map`,
					// TODO encode mappings for output - svelte.preprocess returns decoded mappings
					JSON.stringify(preprocessed.map, null, 2)
				);
			}
			fs.writeFileSync(
				`${outputBase}.js`,
				`${js.code}\n//# sourceMappingURL=${outputName}.js.map`
			);
			fs.writeFileSync(
				`${outputBase}.js.map`,
				JSON.stringify(js.map, null, 2)
			);
			if (css.code) {
				fs.writeFileSync(
					`${outputBase}.css`,
					`${css.code}\n/*# sourceMappingURL=${outputName}.css.map */`
				);
				fs.writeFileSync(
					`${outputBase}.css.map`,
					JSON.stringify(css.map, null, '  ')
				);
			}

			if (js.map) {
				assert.deepEqual(
					js.map.sources.slice().sort(),
					(config.js_map_sources || ['input.svelte']).sort(),
					'js.map.sources is wrong'
				);
			}
			if (css.map) {
				assert.deepEqual(
					css.map.sources.slice().sort(),
					(config.css_map_sources || ['input.svelte']).sort(),
					'css.map.sources is wrong'
				);
			}

			// use locate_1 with mapConsumer:
			// lines are one-based, columns are zero-based

			preprocessed.mapConsumer = preprocessed.map && await new SourceMapConsumer(preprocessed.map);
			preprocessed.locate = getLocator(preprocessed.code);
			preprocessed.locate_1 = getLocator(preprocessed.code, { offsetLine: 1 });

			js.mapConsumer = js.map && await new SourceMapConsumer(js.map);
			js.locate = getLocator(js.code);
			js.locate_1 = getLocator(js.code, { offsetLine: 1 });

			css.mapConsumer = css.map && await new SourceMapConsumer(css.map);
			css.locate = getLocator(css.code || '');
			css.locate_1 = getLocator(css.code || '', { offsetLine: 1 });
			await test({ assert, input, preprocessed, js, css });
		});
	});
});
